#include "cellar.h"
#include <chrono>
std::vector<std::string> &splitSecond(std::string &s, char delim, std::vector<std::string> &elems) {
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, item, delim)) {
        elems.push_back(item);
    }
    return elems;
}

std::vector<std::string> split(std::string &s, char delim) {
    std::vector<std::string> elems;
    splitSecond(s, delim, elems);
    return elems;
}

Cellar::Cellar(std::vector<Wine>& w)
{
    wines = w;
    initParams();
}

void Cellar::initParams()
{
    param_maxForFrequenceTesting = 25;
    param_numOfWinesForAveragePredict = 3; //k-value
    param_thresholdForFrequenceTesting = 0.9;
    param_weightsForFeatures = std::vector<double>(11,1.0);

    mDistanceType = MINKOWSKI;
    qForMinkowski = 0.25; //Most optimal with k=3
}


Cellar::Cellar(std::string& file, std::vector<double> p_low, std::vector<double> p_dif){

    initParams();

    std::vector<Wine> w;
    std::ifstream f(file.c_str());
    std::string s;
    std::vector<double> low(11, 1000);
    std::vector<double> high(11, 0);
    f >> s;
    int count;
    float x;
    while(f >> s){
        std::vector<std::string> att_s = split(s, ',');

        std::vector<double> a;
        std::pair<int, bool> sc;
        count = 0;
        for(auto i = att_s.begin(); i != att_s.end() - 2; i++){
            x = atof(i->c_str());
            a.push_back(x);
            if(low[count] > x)
                low[count] = x;
            if(high[count] < x)
                high[count] = x;
            count++;
        }
        auto i = att_s.end() - 2;
        sc.first = atoi(i->c_str());
        i++;
        sc.second = (*i == "White");
        Wine wi(a, sc);
        w.push_back(wi);
    }
    lim_low = p_low;
    lim_dif = p_dif;
    if(lim_low.empty()){
        lim_low = low;
        for(auto i = 0; i < 11;i++)
            high[i] -= low[i];
        lim_dif = high;
    }
    for(auto i = w.begin(); i != w.end();i++){
        for(auto j = 0; j != 11; j++){
            i->attr[j] = (i->attr[j] - lim_low[j])/(lim_dif[j]);
        }
    }
    wines = w;
    f.close();
}

double Cellar::distanceManhattan(Wine& first, Wine& second)
{
    double temp;
    double distance = 0;
    for(auto j = 0; j != 11; j++){
        temp = first.attr[j] - second.attr[j];
        distance += param_weightsForFeatures[j]*sqrt(temp*temp);
    }

    return distance;
}

double Cellar::distanceSquaredEuclidean(Wine& first, Wine& second)
{
    double temp;
    double distance = 0;
    for(auto j = 0; j != 11; j++){
        temp = (first.attr[j] - second.attr[j]);
        distance += param_weightsForFeatures[j]*temp*temp;
    }

    return distance;
}

double Cellar::distanceMinkowski(Wine& first, Wine& second)
{
    double temp;
    double distance = 0;
    double q = qForMinkowski;
    for(auto j = 0; j != 11; j++){
        temp = param_weightsForFeatures[j]*fabs(first.attr[j] - second.attr[j]);
        distance += pow(temp, q);
    }
    return pow(distance,1/q);
}


void Cellar::find(Wine& w){

    for(auto i = wines.begin(); i != wines.end(); i++){
        if(&w == &(*i)){
            i->temp_distance = 10000; //for running same testing and training sets.
        }
        else if(mDistanceType == MANHATTAN)
        {
            i->temp_distance = distanceManhattan(w,*i);
        }
        else if(mDistanceType == SQUARED_EUCLIDEAN)
        {
            i->temp_distance = distanceSquaredEuclidean(w,*i);
        }
        else if(mDistanceType == MINKOWSKI)
        {
            i->temp_distance = distanceMinkowski(w,*i);
        }
    }
}


std::vector<Wine> Cellar::split_cellar(int a){
    std::vector<Wine> w;
    for(auto i = 0; i < a; i++){
        w.push_back(get_wine());
    }
    return w;
}

Wine Cellar::get_wine(){
    Wine w = wines.back();
    wines.pop_back();
    return w;
}

std::vector<Wine*> Cellar::getWinePointers(){
    std::vector<Wine*> r;
    for(auto i = wines.begin(); i != wines.end(); i++)
        r.push_back(&(*i));
    return r;
}

bool compareWinePointers(Wine* a, Wine* b){
    return a->temp_distance < b->temp_distance;
}

std::pair<int, bool> Cellar::predictClass()
{
    std::vector<Wine*> c_wines = getWinePointers();
    std::partial_sort(c_wines.begin(),c_wines.begin() + param_maxForFrequenceTesting,  c_wines.end(), compareWinePointers);

    return std::make_pair(averagePredict(param_numOfWinesForAveragePredict, c_wines), c_wines[0]->score.second);
}


int Cellar::averagePredict(int num, std::vector<Wine*>& wines_c){
    double sum = 0;
    double average = 0;
    for(auto i = 0; i < num; i++){
       if (wines_c[i]->temp_distance == 0)
            sum += 100000;
       else
            sum += 1/wines_c[i]->temp_distance;
   }
    for(auto i = 0; i < num; i++){
        if (wines_c[i]->temp_distance == 0)
            average += double(wines_c[i]->score.first)*(100000)/sum;
        else
            average += double(wines_c[i]->score.first)*(1/wines_c[i]->temp_distance)/sum;
   }

    return std::round(average);
}

std::pair<Cellar,Cellar> Cellar::splitToRedAndWhite()
{
    std::vector<Wine> reds;
    std::vector<Wine> whites;

    for(auto iter = wines.begin();iter != wines.end(); iter++)
    {
        if(!iter->score.second) //red == false
        {
            reds.push_back(*iter);
        }
        else
        {
            whites.push_back(*iter);
        }
    }

    Cellar redCellar = Cellar(reds);
    Cellar whiteCellar = Cellar(whites);
    copyValuesToAnotherCellar(redCellar);
    copyValuesToAnotherCellar(whiteCellar);

    return std::make_pair(redCellar,whiteCellar);
}


std::pair<std::vector<Wine*>,std::vector<Wine*> > Cellar::splitWineListToRedAndWhite(std::vector<Wine*>& allWines)
{
    std::vector<Wine*> reds;
    std::vector<Wine*> whites;

    for(auto iter = allWines.begin();iter != allWines.end(); iter++)
    {
        if(!(*iter)->predictedType) //red == false
        {
            reds.push_back(*iter);
        }
        else
        {
            whites.push_back(*iter);
        }
    }
    return std::make_pair(reds,whites);
}

void Cellar::copyValuesToAnotherCellar(Cellar &other)
{
    other.setDistanceType((int)mDistanceType);
    other.setMaxForFrequenceTesting(param_maxForFrequenceTesting);
    other.setNumOfWinesForAveragePredict(param_numOfWinesForAveragePredict);
    other.setQforMinkowski(qForMinkowski);
    other.setThresholdForFrequenceTesting(param_thresholdForFrequenceTesting);
    other.setWeightsForWines(param_weightsForFeatures);
}

